﻿#region ... Copyright Notice ...
/*
   Copyright 2008 Tyler Jensen

	Author: Tyler Jensen    http://www.tsjensen.com/blog

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace Atrax.Security
{
	public class KeyManager
	{
		public string CreateLicenseKey(long licenseKeyId)
		{
			//goal -- take userID to create XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
			string X = "X"; //used as the stop character
			int padlen = rand.Next(18) + 1;
			string user = licenseKeyId.ToString().PadLeft(padlen, '0');
			char[] key = new char[25];

			int prev = 0;

			for (int i = 0; i < user.Length; i++)
			{
				int v = int.Parse(char.ToString(user[i]));
				prev += v + i;
				key[i] = mud[prev];
			}
			key[user.Length] = X[0];
			for (int x = user.Length + 1; x < 25; x++)
			{
				int a = rand.Next(250);
				key[x] = mud[a];
			}
			string result = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(key));
			string newkey = result.Substring(0, 5) + "-" + result.Substring(5, 5) + "-"
				+ result.Substring(10, 5) + "-" + result.Substring(15, 5) + "-" + result.Substring(20, 5);
			return newkey;
		}

		public long GetLicenseKeyId(string licenseKey)
		{
			try
			{
				string dekey = licenseKey.Replace("-", "").ToUpper();
				string[] parts = dekey.Split('X');
				string enckey = parts[0];
				int xpre = 0;
				StringBuilder sb = new StringBuilder(3);
				for (int i = 0; i < enckey.Length; i++)
				{
					xpre += i;
					string s = char.ToString(enckey[i]);
					int idx = mud.IndexOf(s, xpre) - xpre;
					xpre += idx;
					sb.Append(idx.ToString());
				}
				string un = sb.ToString();
				long userID = -1;
				long.TryParse(un, out userID);
				return userID;
			}
			catch (Exception e)
			{
			}
			return -1;
		}

		public bool IsValidLicenseKey(string licenseKey)
		{
			try
			{
				long id = this.GetLicenseKeyId(licenseKey);
				if (id > -1)
					return true;
				else
					return false;
			}
			catch
			{
			}
			return false;
		}

		public bool IsValid(string licenseKey, string timeStamp, string authorizationCode)
		{
			if (IsValidLicenseKey(licenseKey))
			{
				string authCode = this.GetAuthorizationCode(licenseKey, timeStamp);
				if (authorizationCode == authCode)
					return true;
				else
					return false;
			}
			else
				return false;
		}

		public string GetAuthorizationCode(string licenseKey, string timeStamp)
		{
			long userId = this.GetLicenseKeyId(licenseKey);
			string privateKey = GetUserPrivateKey(userId);

			if (privateKey.Length == 0) throw new Exception("No private key for this user found.");

			//check MD5 hash of licenseKey + timeStamp + privateKey + "AtraxSaltAndPepper"
			string input = licenseKey + "|" + timeStamp + "|" + privateKey + "|AtraxSaltAndPepper";

			MD5 md5 = MD5CryptoServiceProvider.Create();
			byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
			md5.Clear();

			//authcode should be in base64 string
			return Convert.ToBase64String(hash);
		}

		public string GetAuthenticationToken(string licenseKey, string timeStamp)
		{
			long userId = this.GetLicenseKeyId(licenseKey);
			string input = userId.ToString() + "|" + licenseKey + "|" + timeStamp + "|AtraxSaltAndPepper";
			return EncryptString(input);
		}

		public bool IsValidAuthenticationToken(string authToken)
		{
			string decryptedToken = DecryptString(authToken);
			if (decryptedToken == null) return false;
			string[] parts = decryptedToken.Split('|');
			if (parts.Length == 4 && parts[3] == "AtraxSaltAndPepper")
			{
				string userId = parts[0];
				string licenseKey = parts[1];
				string timeStamp = parts[2];
				if (IsValidAuthenticationTokenTimeStamp(timeStamp))
				{
					long userIdFromKey = GetLicenseKeyId(licenseKey);
					if (userId == userIdFromKey.ToString())
						return true;
					else
						return false;
				}
				else
					return false;
			}
			else
				return false;
		}

		private bool IsValidAuthenticationTokenTimeStamp(string timeStamp)
		{
			//yyyy-MM-dd HH:mm:ss	UTC time
			DateTime dt = DateTime.MinValue;
			if (DateTime.TryParseExact(timeStamp, "yyyy-MM-dd HH:mm:ss", new DateTimeFormatInfo(), DateTimeStyles.None, out dt))
			{
				TimeSpan ts = DateTime.UtcNow - dt;
				if (Math.Abs(ts.TotalHours) > 24)  //auth token is good for 24 hours - if a request takes longer, it is automatically timed out or killed by this rule
					return false;
				else
					return true;
			}
			else
				return false;
		}

		static Dictionary<long, string> privateKeys = null;
		static FileInfo keysInfo = null;
		private string GetUserPrivateKey(long userId)
		{
			string dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string; //a little hack to get the path to the App_Data folder
			string keysFile = Path.Combine(dataDirectory, "pks.config");
			if (privateKeys == null || KeysFileChanged(keysFile))
			{
				privateKeys = new Dictionary<long, string>();
				string[] lines = File.ReadAllLines(keysFile);
				foreach (string line in lines)
				{
					if (line.IndexOf(",") > -1)
					{
						string[] pair = line.Split(',');
						privateKeys.Add(Convert.ToInt64(pair[0]), pair[1]);
					}
				}
				keysInfo = new FileInfo(keysFile); //refresh
			}
			if (privateKeys.ContainsKey(userId))
				return privateKeys[userId];
			else
				return string.Empty;
		}

		private bool KeysFileChanged(string keysFile)
		{
			FileInfo info = new FileInfo(keysFile);
			if (keysInfo == null)
			{
				keysInfo = info;
				return true;
			}
			else
			{
				if (info.LastWriteTimeUtc == keysInfo.LastWriteTimeUtc)
					return false;
				else
					return true;
			}
		}

		//Encryption key and initial vector constants
		static string sharedAuthenticationTokenKey = null;
		static byte[] enckey = null;
		static byte[] enciv = null;

		private void InitializeEncryptionKeys()
		{
			if (sharedAuthenticationTokenKey == null)
			{
				sharedAuthenticationTokenKey = System.Configuration.ConfigurationManager.AppSettings["sharedAuthenticationTokenKey"];
				MD5 md5 = MD5CryptoServiceProvider.Create();
				enckey = md5.ComputeHash(Encoding.UTF8.GetBytes(sharedAuthenticationTokenKey));
				enciv = md5.ComputeHash(Encoding.UTF8.GetBytes("AtraxIVSalt" + sharedAuthenticationTokenKey + "AtraxIVPepper"));
				md5.Clear();
			}
		}

		private string EncryptString(string text)
		{
			InitializeEncryptionKeys();
			RijndaelManaged rjm = new RijndaelManaged();
			rjm.KeySize = 128;
			rjm.BlockSize = 128;
			rjm.Key = enckey;
			rjm.IV = enciv;
			Byte[] input = Encoding.UTF8.GetBytes(text);
			Byte[] output = rjm.CreateEncryptor().TransformFinalBlock(input, 0, input.Length);
			rjm.Clear();
			return Convert.ToBase64String(output);
		}

		private string DecryptString(string text)
		{
			InitializeEncryptionKeys();
			RijndaelManaged rjm = new RijndaelManaged();
			rjm.KeySize = 128;
			rjm.BlockSize = 128;
			rjm.Key = enckey;
			rjm.IV = enciv;
			try
			{
				Byte[] input = Convert.FromBase64String(text);
				Byte[] output = rjm.CreateDecryptor().TransformFinalBlock(input, 0, input.Length);
				return Encoding.UTF8.GetString(output);
			}
			catch
			{
			}
			finally
			{
				rjm.Clear();
			}
			return null; //not decryptable
		}

		public string CreateReturnAuthCode(string licenseKey, string queryId, string clientQueryId, string queryType)
		{
			//look up the licenseKey/user id and secret key
			long userId = this.GetLicenseKeyId(licenseKey);
			string privateKey = this.GetUserPrivateKey(userId);
			string input = licenseKey + "|" + queryId + "|" + clientQueryId + "|" + queryType + "|" + "|AtraxSaltAndPepper"; //todo: consider using a secret key

			MD5 md5 = MD5CryptoServiceProvider.Create();
			byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
			md5.Clear();

			//authcode should be in base64 string
			string code = Convert.ToBase64String(hash);
			return code;
		}


		//DO NOT EVER CHANGE - required to decode license key
		private static Random rand = new Random(DateTime.Now.TimeOfDay.Milliseconds);
		private static readonly string mud = "M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7M4KTF8DW3GBS6PYV2LA5CER9NHZ7";
	}
}
